package net.sf.cldsql.dj.container;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.sf.cldsql.dj.container.base.DiAop;
import net.sf.cldsql.dj.utl.DiException;

public class DiProxy<T> implements InvocationHandler {

    final Logger log = LoggerFactory.getLogger(DiProxy.class);

    private T delegate;
    private DiAop<T>[] aops;

    private T proxy;

    private String pattern;
    
    static final Method toString;
    static {
        try {
            toString = Object.class.getMethod("toString", (Class[]) null);
        } catch (SecurityException e) {
            throw new DiException (e);
        } catch (NoSuchMethodException e) {
            throw new DiException (e);
        }
    }
    
    @SuppressWarnings("unchecked")
    public DiProxy (DiContainer container, T delegate, Class<DiAop<T>>[] aopClass, String pattern) {
        this.delegate = delegate;
        this.pattern = pattern;
        
        try {
            aops = new DiAop[aopClass.length];
            for (int i = 0; i < aopClass.length; i ++) {
                this.aops[i] = aopClass[i].newInstance();
                this.aops[i].setContainer(container);
            }
        } catch (InstantiationException e) {
            throw new DiException(e);
        } catch (IllegalAccessException e) {
            throw new DiException(e);
        }
        for (DiAop<T> aop : aops) {
            log.debug("Bind aop {} to {}", aop, delegate);
            delegate = aop.beforeCreate(delegate, null, null);
            this.proxy = (T)Proxy.newProxyInstance(
                    this.delegate.getClass().getClassLoader(), 
                    this.delegate.getClass().getInterfaces(), 
                    this);
            this.proxy = aop.afterCreate(delegate, null, null, proxy);
        }
        
        log.debug("Proxy {} ceated", proxy.getClass());
    }

    /**
     * @param delegate
     * @param proxy
     * @return
     */
    public T getProxy() {
        return proxy;
    }
    /** */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if (method.getName().matches(pattern)) {
            // Only methods, with the first parameter is DiContext, are aop target.
            log.debug("Invoke {}.{}({})", proxy.getClass(), method, args);
            Object result = null;
            try {
                //call before
                for (DiAop<T> aop : aops) {
                    aop.before(delegate, method, args);
                    //aop.getClass()
                    //.getDeclaredMethod("before", new Class[] {Method.class})
                    //.invoke(aop, new Object[] {method});
                }
                
                //call original method
                result = method.invoke(this.delegate, args);
                
                //call after
                for (int i = aops.length - 1; i >= 0; i --) {
                    DiAop<T> aop = aops[i];
                    result = aop.after(delegate, method, args, result);
                    //aop.getClass()
                    //.getDeclaredMethod("after", new Class[] {Method.class})
                    //.invoke(aop, new Object[] {method});
                }
            } catch (Exception e) {
                //do when exception
                for (int i = aops.length - 1; i >= 0; i --) {
                    DiAop<T> aop = aops[i];
                    aop.error(delegate, method, args, e);
                    //aop.getClass()
                    //.getDeclaredMethod("error", new Class[] {Method.class})
                    //.invoke(aop, new Object[] {method, e});
                }
            } finally {
                //do when exception
                for (DiAop<T> aop : aops) {
                    aop.finalli(delegate, method, args);
                    //aop.getClass()
                    //.getDeclaredMethod("error", new Class[] {Method.class})
                    //.invoke(aop, new Object[] {method, e});
                }
            }
            log.debug("Invoke return {}", result);
            return result;
        } else {
            //call original method
            return method.invoke(this.delegate, args);
        }
    }

}
